/***********************************************************************************************************************
 * ############################################# Application AdewebLite #############################################
 * AdewebLite Android est une application permettant de consulter des emplois du temps fournis par l'université UHP
 * avec ou sans connexion internet (informations gardées en mémoire à chaque synchronisation). Et utilisant la web
 * API ADE pour collecter les données.
 * @author Andrianjafintrimo Victor, Zanette Sébastien
 * 
 * Basée sur l'application du même nom, utilisant le flux RSS (données à traiter), de :
 * @author Schnell Michaël, Zanette Sébastien
 * **********************************************************************************************************************
 */

package ade.schedule;

import ade.analyzer.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import ade.database.Database;
import ade.exception.AAAException;
import ade.exception.AAAInternalException;
import ade.exception.EmptyFieldException;
import ade.exception.InvalidValueException;
import android.database.Cursor;

/**
 * Structure pour un emploi du temps.
 * Un emploi du temps connait une liste de cours.
 * Contient un ensemble de fonctions permettant de contrôler les informations fournis par l'utilisateur lors de la création d'un emploi du temps
 * et d'initialiser la contruction des cours.
 * @author Zanette Sébastien, @author Andrianjafintrimo Victor
 */
public class Schedule implements Iterable<Lesson> {

	// HAS TO BE SORTED !
	public static final int[] VALID_PROJECT_IDS = new int[]{1, 2} ;

	private static final int NAME_MAX_LENGTH = 50 ;

	private static final String RESSOURCE_FORMAT  = "g?[0-9]+" ;
	private static final String RESSOURCES_FORMAT = 
			String.format("%1$s([ ]*\\|[ ]*%1$s)*", RESSOURCE_FORMAT) ;

	private static final String STRING_DISPLAY_FORMAT = "--------------------\nNom : '%s'\n" +
			"ProjectID : '%s'\nRessources : '%s'\nNuméro étudiant : '%s'\nPériode : '%s'\nDer modif : '%s'\n" +
			"Crée le : '%s'\n--------------------" ;

	protected int projectId ;
	protected String name ;
	protected String ressources ;
	protected int period ;
	protected String lastModif ;
	protected int status ;
	protected String created ;
	protected Lessons lessons ;
        protected String studentNum;
        protected String ressStudentNum;
        protected ArrayList<StudentNumber> studentNumList;

	/**
	 * Initialisation d'un emploi du temps avec les champs données en paamètre.
         * 
	 * @param projectId Projet AdeWeb identifiant une partie de l'Université de Lorraine
	 * @param name Nom de l'emploi du temps donnée pa l'utilisateur
	 * @param ressources Numéro de ressources donnée par l'utilisateur
	 * @param period Période pour laquelle synchroniser l'emploi du temps
	 * @param lastModif Date de dernière synchronisation de l'emploi du temps
	 * @param status Statut de synchronisation (non synchro, en attente de synchro, en synchronisation, synchronisé)
	 * @param created Date de crétion de l'emploi du temps
         * @param studentNum Numéro(s) étudiant donnée(s) par l'utilisateur
	 */
	public Schedule(int projectId, String name, String ressources, int period, 
			String lastModif, int status, String created, String studentNum) {
		super() ;
		this.projectId = projectId;
		this.name = name.trim();
		this.ressources = ressources.trim() ;
		this.period = period;
		this.lastModif = lastModif;
		this.status = status;
		this.created = created;
		this.lessons = new Lessons() ;
                this.studentNum = studentNum;
                this.ressStudentNum="";
                this.studentNumList = new ArrayList<StudentNumber>();               
	}

        /**
         * Initialisation d'un emploi du temps avec les valeurs par défaut
         */
	public Schedule() {
		this(0, "", "", 0, "", 0, "","") ;
	}

        /**
         * Positionne le champ created à la valeur donnée en paramètre.
         * @param created date de création de l'emploi du temps
         */
	public void setCreated(String created) {
		this.created = created;
	}

        /**
         * Positionne le champ lastmodif à la valeur donnée en paramètre.
         * @param lastModif date de dernière mise à jour
         */
	public void setLastModif(String lastModif) {
		this.lastModif = lastModif;
	}

        /**
         * Positionne le champ name à la valeur donnée en paramètre.
         * @param name nom de l'emploi du temps
         */
	public void setName(String name) {
		this.name = name;
	}

        /**
         * Positionne le champ status à la valeur donnée en paramètre.
         * @param status entier representant le statut de synchronisation (non synchro, en attente de synchro, en synchronisation, synchronisé)
         */
	public void setStatus(int status) {
		this.status = status;
	}

        /**
         * Positionne le champ period à la valeur donnée en paramètre.
         * @param period Période pour laquelle synchroniser l'emploi du temps
         */
	public void setPeriod(int period) {
		this.period = period;
	}

        /**
         * Positionne le champ projectId à la valeur donnée en paramètre.
         * @param projectId Projet AdeWeb identifiant une partie de l'Université de Lorraine
         */
	public void setProjectId(int projectId) {
		this.projectId = projectId;
	}

        /**
         * Positionne le champ ressources à la valeur donnée en paramètre.
         * @param ressources Numéro de ressources donnée par l'utilisateur
         */
	public void setRessources(String ressources) {
		this.ressources = ressources;
	}
        
        /**
         * Positionne le champ studentNum à la valeur donnée en paramètre.
         * @param studentNum Numéro(s) étudiant donnée(s) par l'utilisateur
         */
        public void setStudentNum(String studentNum) {
            this.studentNum = studentNum;
        }

        /**
         * Retourne la valeur du champs created.
         * @return date de création de l'emploi du temps
         */
	public String getCreated() {
		return this.created;
	}

        /**
         * Retourne la valeur du champs lastModif.
         * @return date de la dernière mise à jour
         */
	public String getLastModif() {
		return this.lastModif;
	}

        /**
         * Retourne la valeur du champs name.
         * @return Nom de l'emploi du temps
         */
	public String getName() {
		return this.name;
	}

        /**
         * Retourne la valeur du champs status.
         * @return Entier representant le statut de synchronisation (non synchro, en attente de synchro, en synchronisation, synchronisé)
         */
	public int getStatus() {
		return this.status;
	}

        /**
         * Retourne la valeur du champs period.
         * @return Période pour laquelle synchroniser l'emploi du temps
         */
	public int getPeriod() {
		return this.period;
	}

        /**
         * Retourne la valeur du champs projectId.
         * @return Projet AdeWeb identifiant une partie de l'Université de Lorraine
         */
	public int getProjectId() {
		return this.projectId;
	}
        
        /**
         * Retourne la valeur du champs studentNum.
         * @return Numéro(s) étudiant donnée(s) par l'utilisateur
         */
        public String getStudentNum(){
            return this.studentNum;
        }
        
        /**
         * Retourne la liste de numéros étudiants de l'emploi du temps.
         * @return 
         */
        public ArrayList<StudentNumber> getStudentNumList() {
            return this.studentNumList;
        }
        
        /**
         * Construit la liste de <code>StudentNumber</code> avec les numéros de ressources associés au(x) numéro(s) étudiant obtenu grâce à l'API ADEWEB.
         */
        public void setStudentNumList() {
                //Connexion à la webAPI
                FeedConnexion connexion = ContainerData.getFeedConnexion();
                if (connexion != null) {
                    String sessionId = connexion.getId();
                    //Selection du projet
                    connexion = ContainerData.getFeedSetProject(sessionId,this.projectId);

                    //Cas d'un seul numéro étudiant
                    if(!studentNum.equals("") && !studentNum.contains("|")){
                      StudentNumber s = new StudentNumber();
                        s.setStudentNumber(studentNum);
                        //Récupération des ressources associées à un numéro étudiant
                        ArrayList<FeedResourceEtud> resNum = ContainerData.getFeedsResources(sessionId,studentNum) ;
                        for(FeedResourceEtud r : resNum){
                           s.addRessource(r.getId());
                        }
                        this.studentNumList.add(s);

                    }else{
                        for(String sn :this.studentNum.split("\\|")){
                            StudentNumber s = new StudentNumber();
                            s.setStudentNumber(sn);
                            //Récupération des ressources associées à un numéro étudiant
                            ArrayList<FeedResourceEtud> resNum = ContainerData.getFeedsResources(sessionId,sn) ;
                            for(FeedResourceEtud r : resNum){
                            s.addRessource(r.getId());
                            }
                            this.studentNumList.add(s);
                        }
                    }
                    StringBuilder sn = new StringBuilder();
                    for(StudentNumber s : this.studentNumList){
                        if(!sn.toString().equals(""))
                            sn.append("|");
                        sn.append(s.getRessources());
                    }
                    ressStudentNum = sn.toString();

                //Deconnexion de la webAPI
                    connexion = ContainerData.getFeedDeconnexion(sessionId);
                }
        }
        
        /**
         * Construit et fixe la chaine de caractères de ressources obtenu depuis les numéros étudiants.
         */
        public void setStudentNum() {
            String res ="";
            if (!this.studentNum.equals("")) {
                StringBuilder s = new StringBuilder();
                Database db = new Database();
                String[] studNum = this.studentNum.split("\\|");
                for(String sn : studNum) {
                    s.append(db.getRessourceWithStudentNumber(sn));
                }
                res = s.toString();
            }
            this.ressStudentNum = res;
        }
        /**
         * Retourne une chaine contenant l'ensemble des ressources données par l'utilisateur et obtenu à partir de numéro(s) étudiant.
         * @return chaine contenant l'ensemble des ressources données par l'utilisateur et obtenu à partir de numéro(s) étudiant
         */
        public String getAllResources(){
            StringBuilder s = new StringBuilder("");
            if(!ressources.equals("") && !ressStudentNum.equals("")){
                   s.append(ressources);
                   s.append("|");
                   s.append(ressStudentNum);
                }else if(ressources.equals("")){
                    s.append(ressStudentNum);
                }else if(ressStudentNum.equals("")){
                    s.append(ressources);
                }
            return s.toString();
        }
        
	/**
	 * Retourne la valeur du champ ressources.
	 * @return Numéro de ressources donnée par l'utilisateur
	 */
	public String getRessources() {
		return this.ressources ;
	}// getRessources()

	/**
	 * Retourne une chaine contenant la valeur des différents champs.
         * Sous la forme : "--------------------\nNom : '%s'\n" +
			"ProjectID : '%s'\nRessources : '%s'\nNuméro étudiant : '%s'\nPériode : '%s'\nDer modif : '%s'\n" +
			"Crée le : '%s'\n--------------------"
         * 
         * @return chaine contenant la valeur des différents champs
         */
	public String toString(){
		return String.format(Schedule.STRING_DISPLAY_FORMAT, this.name, this.projectId, 
				this.ressources,this.studentNum, this.period, this.lastModif, this.status, this.created) ;
	}// toString()

	/**
         * Permet de contrôler les informations entrées par l'utilisateur.
         * <ul><li>Chaque nom d'emploi du temps doit être unique</li>
         * <li>Aucun champ doit être vide</li>
         * <li>Verifier le format des ressources</li></ul>
         * 
         * @param db Instance de la Base de Données pour un contexte
         * @throws AAAException 
         */
	public void validate(Database db) throws AAAException {
		// ------------------------------------------------------------------------------------------------------
		// Check that no field is empty
		// ------------------------------------------------------------------------------------------------------
		if (this.name.equals("")) {
			throw new EmptyFieldException("Pas de nom donné pour l'emploi du temps.") ;
		}// if
		if (this.ressources.equals("") && this.studentNum.equals("")) {
			throw new EmptyFieldException("Pas de ressources ou pas de numéro etudiant données pour l'emploi du temps.") ;
		}// if
		if (this.created.equals("")) {
			throw new AAAInternalException("Date de création manquante.") ;
		}// if
		if (this.lastModif.equals("")) {
			throw new AAAInternalException("Date de dernière modification manquante.") ;
		}// if

		// ------------------------------------------------------------------------------------------------------
		// Check the given values
		// ------------------------------------------------------------------------------------------------------
		// The projectId can only have limited values
		if (Arrays.binarySearch(Schedule.VALID_PROJECT_IDS, this.projectId) < 0) {
			throw new InvalidValueException("Veuillez choisir un établissement valide.") ;
		}// if
		// The name has to be unique and is limited in size
		if (this.name.length() > NAME_MAX_LENGTH) {
			throw new InvalidValueException("Le nom pour l'emploi du temps est trop long.") ;
		}// if

		for(String name : db.getSchedulesName()) {
			if (name.equals(this.name)) {
				throw new InvalidValueException("Il existe déjà un emploi du temps avec ce nom.") ;
			}// if
		}// for
		// The ressources can only have the folloing format
		if (!this.ressources.equals("") && !this.ressources.matches(Schedule.RESSOURCES_FORMAT)) {
			throw new InvalidValueException("Les ressources doivent avoir l'un des formats suivants g suivi de chiffres ou juste des chiffres.") ;
		}// if
	}// validate()

        /**
         * Permet de contrôler les informations entrées par l'utilisateur, lors d'une mise à jour d'un emploi du temps.
         * Mêmes contrôles que la fonction {@link #validate(ade.database.Database)  validate()}, mais permet de changer le nom de l'emploi du temps.
         * @throws AAAException 
         */
	public void validateOnUpdate() throws AAAException {
		// ------------------------------------------------------------------------------------------------------
		// Check that no field is empty
		// ------------------------------------------------------------------------------------------------------
		if (this.ressources.equals("") && this.studentNum.equals("")) {
			throw new EmptyFieldException("Pas de ressources ou pas de numéro etudiant données pour l'emploi du temps.") ;
		}// if
		if (this.created.equals("")) {
			throw new AAAInternalException("Date de création manquante.") ;
		}// if
		if (this.lastModif.equals("")) {
			throw new AAAInternalException("Date de dernière modification manquante.") ;
		}// if

		// ------------------------------------------------------------------------------------------------------
		// Check the given values
		// ------------------------------------------------------------------------------------------------------
		// The projectId can only have limited values
		if (Arrays.binarySearch(Schedule.VALID_PROJECT_IDS, this.projectId) < 0) {
			throw new InvalidValueException("Veuillez choisir un établissement valide.") ;
		}// if
		// The ressources can only have the folloing format
		if (!this.ressources.equals("") && !this.ressources.matches(Schedule.RESSOURCES_FORMAT)) {
			throw new InvalidValueException("Les ressources doivent avoir l'un des formats suivants g suivi de chiffres ou juste des chiffres.") ;
		}// if
	}//validateOnUpdate()

        /**
         * Construction des cours, récupération depuis l'API.
         * @throws AAAException 
         */
	public void build() throws AAAException {
		//Connexion à la webAPI
                FeedConnexion connexion = ContainerData.getFeedConnexion();
                if (connexion != null) {                   
                    String sessionId = connexion.getId();
                    //Selection du projet
                    connexion = ContainerData.getFeedSetProject(sessionId,this.projectId);
                    //Obtention des cours
                    //Instancie la liste de StudentNumber d'un emploi du temps à partie du champs studentNum
                    //this.setStudentNumList(sessionId);

                    ArrayList<FeedLesson> feedLessons = ContainerData.getFeedsLesson(sessionId, this.getAllResources(), this.period) ;   
                    // Analyse the feeds and build this instance of Schedule
                    FeedAnalyzer fa = new FeedAnalyzer(this, feedLessons) ;
                    fa.start() ;
                    //Decoonnexion de la webAPI
                    connexion = ContainerData.getFeedDeconnexion(sessionId);
                }
                else {
                    throw new AAAException();
                }
                
	}// build()

        /**
         * Construction des cours.
         * @param db Base de Données
         */
	public void build(Database db) {
                Lesson[] tabLessons = db.getLessonByScheduleName(this.getName());
                this.ensureCapacity(tabLessons.length) ;
                for (Lesson l :tabLessons){
                    this.append(l);
                }
		/*Cursor lessonsCursor = db.getLessonsCursor(this) ;
		this.ensureCapacity(lessonsCursor.getCount()) ;
		Lesson lesson = Database.cursorToLesson(lessonsCursor) ;
		while (lesson != null) {
			this.append(lesson) ;
			lesson = Database.cursorToLesson(lessonsCursor) ;
		}// while
		lessonsCursor.close() ;*/
		db.close() ;
	}// build()
        
        /**
         * Construction des cours.
         * @param db Base de Données
         */
	public void buildComplete(Database db) {
		Cursor lessonsCursor = db.getAllLessonsCursor(this) ;
		this.ensureCapacity(lessonsCursor.getCount()) ;
		Lesson lesson = Database.cursorToLesson(lessonsCursor) ;
		while (lesson != null) {
			this.append(lesson) ;
			lesson = Database.cursorToLesson(lessonsCursor) ;
		}// while
		lessonsCursor.close() ;
		db.close() ;
	}// build()

        /**
         * Insert un cours dans la liste de cours.
         * @param l cours
         */
	public void insert(Lesson l) {
		this.lessons.insert(l) ;
	}// add(Lesson)

        /**
         * Augmente la capacité du tableau de <code>Lesson</code>.
         * @param size taille du tableau
         */
	public void ensureCapacity(int size) {
		this.lessons.ensureCapacity(size) ;
	}// ensureCapacity(int) ;

        /**
         * Permet d'itérer sur le type <code>Lesson</code>.
         * @return <code>Iterator<Lesson></code>
         */
	public Iterator<Lesson> iterator() {
		return new Iterator<Lesson>() {
			int next = 0 ;

			public boolean hasNext() {
				return (this.next < Schedule.this.lessons.size()) ;
			}// hasNext()

			public Lesson next() {
				return Schedule.this.get(this.next++) ;
			}// next()

			public void remove() {
				throw new UnsupportedOperationException("ScheduleIterator.remove n'a pas été implanté.") ;
			}
		} ;
	}// iterator()

        /**
         * Retourne un cours de la liste à l'indice donné en paramètre.
         * @param i
         * @return le cours
         */
	protected Lesson get(int i) {
		return this.lessons.get(i) ;
	}// get(int)

        /**
         * Retroune la liste des cours.
         * @return les cours
         */
	public Lessons getLessons() {
		return this.lessons;
	}

        /**
         * Ajoute un cours à la suite de la liste.
         * @param l cours
         */
	public void append(Lesson l) {
		this.lessons.append(l) ;
	}// append(Lesson)

        /**
         * Retourne la taille du tableau de cours.
         * @return 
         */
	public int getNLessons() {
		return this.lessons.size() ;
	}// getNLessons()
	
        /**
         * Fonction de comparaison de 2 Objets <code>Lesson</code>.
         * @param o
         * @return vrai si les champs des cours sont identiques, faux sinon
         */
	@Override
	public boolean equals(Object o) {
		if (o instanceof Schedule) {
			Schedule other = (Schedule) o ;
			return this.name.equals(other.name) && this.ressources.equals(other.ressources) && (this.studentNum.equals(other.studentNum))
					&& (this.projectId == other.projectId) && (this.period == other.period) ;
		}// if
		return super.equals(o) ;
	}// equals(Object)

}// class Schedule
